home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-05-01 | 46.9 KB | 1,318 lines | [TEXT/MPS ] |
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #
- # Apple Macintosh Developer Technical Support
- #
- # SoundUnit
- #
- # SoundUnit.p - MPW 3.0/3.1 Pascal Source
- #
- # Versions:
- # 1.03 May, 1990
- #
- # Components:
- # SoundApp.make May 1, 1990 MPW build script
- # SoundApp.p May 1, 1990 Pascal source code
- # SoundApp.r May 1, 1990 Rez source code
- # SoundAppSnds.r May 1, 1990 Rez source code
- # SoundUnit.p May 1, 1990 Pascal source code
- #
- # Formatting was done with FONT = Monaco, SIZE = 9, TABS = 3
- #
- # SoundApp.p is a sample application source file for demonstrating the
- # Sound Manager. This portion of the source code handles the Sound Manager
- # part of the application. This UNIT can be used by others.
- #
- # Jim Reekes E.O., Macintosh Developer Technical Support
- # Tuesday, January 30, 1990 1:01 PM
- #
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- UNIT SoundUnit;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- INTERFACE
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- USES
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {Only include the interface files that I really need. This helps MPW
- to compile faster.}
-
- Types, Memory, Resources, OSUtils, Errors, FixMath, SANE, Sound;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- CONST
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {refer to the SoundAppSnds.r file for documentation on these note values}
- kOctave1 = 0; {octaves of MIDI note values}
- kOctave2 = 12;
- kOctave3 = 24;
- kOctave4 = 36;
- kOctave5 = 48;
- kOctave6 = 60;
- kOctave7 = 72;
- kOctave8 = 84;
- kOctave9 = 96;
- kOctave10 = 108;
- kOctave11 = 120;
-
- Akey = -3; {the note A}
- Bbkey = -2; {the note B flat}
- Bkey = -1; {the note B}
- Ckey = 0; {the note C}
- Dbkey = 1; {the note D flat}
- Dkey = 2; {the note D}
- Ebkey = 3; {the note D flat}
- Ekey = 4; {the note E}
- Fkey = 5; {the note F}
- Gbkey = 6; {the note G flat}
- Gkey = 7; {the note G}
- Abkey = 8; {the note A flat}
-
- {These are other constants used in the SoundUnit}
- kInitNone = 0; {no init options}
- kWait = FALSE; {wait for the channel}
- kSMAsynch = TRUE; {asynchronous Sound Manager call}
- kMiddleC = kOctave6 + Ckey; {MIDI note value of middle C}
- kWaveSize = 512; {standard size of wave table}
- kSyncID = $12345678; {identifier used in syncCmd}
- kOneSecond = 2000; {one second note duration}
- kFullAmp = $FF000000; {full amplitude setting}
-
-
- {These determine the type of sound header is in a sampled sound.
- The most common is standard. MACE using compressed. Comment these out
- for MPW 3.2 and later.}
- stdSH = $00; {standard sound header}
- extSH = $FF; {extended sound header}
- cmpSH = $FE; {compressed sound header}
-
- {These are used as flags in the sound channel to determine the state
- of that channel. The 'snth' IDs are used when the channel is in use
- to determine what that channel in intended for.}
- kNoSynth = 0; {no synth is specified}
- kChanComplete = -1; {channel has completed}
- kChanFree = MAXLONGINT; {channel is not in use}
- kSoundComplete = $1234; {flag for callBackCmd}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- TYPE
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- SndCmdPtr = ^SndCommand; {Ptr to a sound command, for type coersion}
- IntPtr = ^INTEGER; {Ptr to a INTEGER, for type coersion}
-
- {I have declared a few TYPEs below to help examine 'snd ' resources.
- I have to break them up into individual pieces because they are
- variable sized records.}
-
- Snd1Header = RECORD
- format: INTEGER;
- numSynths: INTEGER;
- END;
- Snd1HdrPtr = ^Snd1Header;
- Snd1HdrHndl = ^Snd1HdrPtr;
-
- SynthInfo = RECORD
- synthID: INTEGER;
- initOption: LONGINT;
- END;
- SynthInfoPtr = ^SynthInfo;
-
- Snd2Header = RECORD
- format: INTEGER;
- refCount: INTEGER;
- END;
- Snd2HdrPtr = ^Snd2Header;
- Snd2HdrHndl = ^Snd2HdrPtr;
-
- {I have created my own sound channel type. It extends the normal
- sound channel by adding a few fields. To confirm that the channel
- in question is mine, I set the userInfo field to something that I
- can recognize. I keep the 'snd ' resource handle associated to this
- channel too. This allows me to dispose of the data once the channel
- has completed its duties.}
-
- MyChanType = RECORD {this is a 1064 byte structure}
- theChan: SndChannel; {must keep sound channel as first field}
- dataHandle: Handle;
- END;
- MyChanPtr = ^MyChanType;
-
- {I’ve had to re-define the SoundHeader to correctly match the current version.
- This is correctly defined in MPW 3.2 or later.}
-
- MySndHeader = PACKED RECORD
- samplePtr: Ptr; {if NIL then samples are in sampleArea}
- length: LONGINT;
- sampleRate: Fixed;
- loopStart: LONGINT;
- loopEnd: LONGINT;
- encode: BYTE; {special header type field}
- baseNote: BYTE; {base note now byte}
- sampleArea: PACKED ARRAY [0..0] OF Byte;
- END;
- MySndHeaderPtr = ^MySndHeader;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- VAR {The “g” prefix is used to emphasize that a variable is global.}
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {These four global pointers are to my sound channels. One of them has to
- be global and I could utilize the fact that they are a linked list. That
- would complicate things to some degree, and it really wouldn’t have that
- much of an advantage.}
-
- gChan1: MyChanPtr;
- gChan2: MyChanPtr;
- gChan3: MyChanPtr;
- gChan4: MyChanPtr;
-
- {This is the global flag that is set once the sound channel’s call back
- procedure has been called. This, in my case, means the channel has
- completed its duties and is time for disposing.}
-
- gCalledBack: BOOLEAN;
-
- {gChanOpen is a flag set to determine if the application has a sound
- channel open. It’s really not feasible to determine if a sound is being
- made at any given point.}
-
- gChanOpen: BOOLEAN;
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {The routines below are for public consumption in this UNIT. Note that
- these all return standard sound channels to allow easy of modifications.
- I can change the structure of my own sound channels and the code without
- forcing a change in the application that uses this UNIT.}
-
- PROCEDURE _SANELib;
- PROCEDURE _SoundUnit;
-
- FUNCTION InitSoundUnit: OSErr;
- FUNCTION SoundCompletion: BOOLEAN;
- FUNCTION SndChanOpen: BOOLEAN;
- FUNCTION SetNoteTimbre(noteChan: SndChannelPtr; timbre: INTEGER;
- immediate: BOOLEAN): OSErr;
- FUNCTION SendNote(chan: SndChannelPtr; duration: INTEGER; note: LONGINT): OSErr;
- FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
- FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
- PROCEDURE DoSoundComplete;
- PROCEDURE FreeAllChans;
- PROCEDURE FreeSoundUnit;
- FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
- FUNCTION HoldSnd(sndHandle: Handle): OSErr;
- FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
- FUNCTION GetSndDataOffset(sndHandle: Handle;
- VAR dataType, waveLength: INTEGER): LONGINT;
- FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
- sndInstrument: Handle): OSErr;
- FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
- waveLength: INTEGER): OSErr;
- FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
- waveChan3, waveChan4: SndChannelPtr): OSErr;
- FUNCTION GetNoteChan(VAR noteChan: SndChannelPtr; timbre: INTEGER): OSErr;
- FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
- FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
- FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
- FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
- song1, song2, song3, song4: Handle): OSErr;
- FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
- FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- IMPLEMENTATION
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SANELib}
- PROCEDURE _SANELib;
-
- {This is a dummy routine to allow the application to unload the SANE library
- that this UNIT uses.}
-
- BEGIN
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE _SoundUnit;
-
- {This is a dummy routine to allow the application to unload this UNIT.}
-
- BEGIN
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Initialize}
- FUNCTION InitSoundUnit: OSErr;
-
- {Create storage for each of the four channels (4 * 1064 bytes) and
- initialize them. If any of the channels cannot be allocated, return an
- error. Also initialized our global flag “gCalledBack.” An interesting
- modification would be to allow the caller to pass in the number of
- channels the application really intends on using. If the user only wants
- one channel, then we could just allocate one instead of four. These
- channels are used at interrupt time.}
-
- FUNCTION InitMyChan(VAR newChan: MyChanPtr): BOOLEAN;
- BEGIN
- newChan:= MyChanPtr(NewPtrClear(SizeOf(MyChanType)));
- IF newChan <> NIL THEN BEGIN
- newChan^.theChan.qLength:= stdQLength;
- newChan^.theChan.userInfo:= kChanFree;
- InitMyChan:= TRUE;
- END ELSE
- InitMyChan:= FALSE;
- END;
-
- BEGIN
- gChanOpen:= FALSE;
- gCalledBack:= FALSE;
- IF InitMyChan(gChan1) THEN
- IF InitMyChan(gChan2) THEN
- IF InitMyChan(gChan3) THEN
- IF InitMyChan(gChan4) THEN;
- InitSoundUnit:= MemError;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION SoundCompletion: BOOLEAN;
-
- {This routine can be called to determine if the sound has completed. When
- this is true, the sound data can be disposed of. The global “gCalledBack”
- determines whether the sound has completed or not and it is set by the
- sound channel’s completion routine. Soundcompletion is placed in the Main
- segment because it is called by the event loop.}
-
- BEGIN
- SoundCompletion:= gCalledBack;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION SndChanOpen: BOOLEAN;
-
- {This routine can be called at any time. It will return TRUE when the
- SoundUnit has an open channel. This can be can considered the same as
- sound being active. As long as the channel is open, no other channels can
- be opened. It is placed in the Main segment because it is called by the
- event loop.}
-
- BEGIN
- SndChanOpen:= gChanOpen;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SetNoteTimbre(noteChan: SndChannelPtr; timbre: INTEGER;
- immediate: BOOLEAN): OSErr;
-
- {Given a channel and timbre (sounds like “tom burr”), this will adjust the
- tone quality of the note synthesizer. Changing the tone can only be done
- before playing a note. On a Mac with the Apple Sound Chip, this can be
- done in real time while a note is playing. But, since there’s no
- supported method for determining if the ASC is available I have to assume
- that it’s not. I use the immediate flag to determine if the user wants to
- change the timbre now, or queue the command. If the queue is full, it
- will wait for the command to be accepted.
-
- BUG NOTE: There is a bug in the Sound Manager running on the Mac Plus or
- SE where sending a timbreCmd with a timbre of 255 (a legal value) will
- crash. The difference between 254 and 255 isn’t audible, so I only allow
- a maximum of 254 in any case.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- IF timbre > 254 THEN
- timbre:= 254;
- WITH theCmd DO BEGIN
- cmd:= timbreCmd;
- param1:= timbre;
- param2:= 0;
- END;
- IF immediate THEN
- SetNoteTimbre:= SndDoImmediate(noteChan, theCmd)
- ELSE
- SetNoteTimbre:= SndDoCommand(noteChan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SendNote(chan: SndChannelPtr; duration: INTEGER; note: LONGINT): OSErr;
-
- {Given a channel and note information, this will place the note into the
- channel’s queue. The note contains the amplitude and note value. It is a
- four-byte parameter with the high byte containing the amplitude. I use
- SndDoCommand with the noWait flag set to wait for the channel to except
- the command in case the queue is currently full.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= noteCmd;
- param1:= duration;
- param2:= note;
- END;
- SendNote:= SndDoCommand(chan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SendQuiet(chan: SndChannelPtr; immediate: BOOLEAN): OSErr;
-
- {Given a channel, this will place a quietCmd into the channel’s queue. I
- use SndDoCommand with the noWait flag set to wait for the channel to
- accept the command in the case it is currently full.
-
- BUG NOTE: A sequence of notes and rests will not work unless quietCmds
- are between them. Rests have to be made quiet before they rest, if that
- makes any more sense. A noteCmd will loop, causing the sound in progress
- to continue, until a quietCmd is received.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= quietCmd;
- param1:= 0;
- param2:= 0;
- END;
- IF immediate THEN
- SendQuiet:= SndDoImmediate(chan, theCmd)
- ELSE
- SendQuiet:= SndDoCommand(chan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SendRest(chan: SndChannelPtr; duration: INTEGER): OSErr;
-
- {Given a channel and duration, this will place the rest into the channel’s
- queue. Before sending a rest a quietCmd is needed. Rests don’t work
- unless you tell the Sound Manager to be quiet too. I use SndDoCommand
- with the noWait flag set to wait for the channel to accept the command in
- case it is currently full.}
-
- VAR
- theCmd: SndCommand;
- theErr: OSErr;
-
- BEGIN
- theErr:= SendQuiet(chan, kWait);
- IF theErr = noErr THEN BEGIN
- WITH theCmd DO BEGIN
- cmd:= restCmd;
- param1:= duration;
- param2:= 0;
- END;
- theErr:= SndDoCommand(chan, theCmd, kWait);
- END;
- SendRest:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE FreeChan(myChan: MyChanPtr);
-
- {Test if the channel is free, and if not then call SndDisposeChannel.
- This will release the synthesizer (snth resource) code and the
- required hardware. If we didn’t do this, no other channels would
- work. I also test myChan for having a snd resource attached to the
- channel. If so, then I mark it as purgeable and reset the data to NIL.
-
- BUG NOTE: Calling SndDisposeChannel while or immediately after playing
- a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
- Issuing a quietCmd first kept the Sound Manager happy and my Mac from
- crashing.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- IF myChan^.theChan.userInfo <> kChanFree THEN BEGIN
- theErr:= SendQuiet(SndChannelPtr(myChan), NOT kWait); {ignore error}
- theErr:= SndDisposeChannel(SndChannelPtr(myChan), NOT kWait);
- myChan^.theChan.userInfo:= kChanFree;
- END;
- IF myChan^.dataHandle <> NIL THEN BEGIN
- HUnlock(myChan^.dataHandle);
- HPurge(myChan^.dataHandle);
- myChan^.dataHandle:= NIL;
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE DoSoundComplete;
-
- {This routine is called by an application that established a sound to be
- played asynchronously. This is, in effect, the routine to be used after
- the completion routine has been called. The application should call this
- once SoundCompletion returns TRUE. In the case the application is using
- multiple channels, we will only free a channel once it has been marked as
- kChanComplete. I do not reset the gCalledBack until all the open channels
- are freed.}
-
- BEGIN
- IF gChan1^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan1);
- IF gChan2^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan2);
- IF gChan3^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan3);
- IF gChan4^.theChan.userInfo = kChanComplete THEN
- FreeChan(gChan4);
-
- IF (gChan1^.theChan.userInfo = kChanFree)
- & (gChan2^.theChan.userInfo = kChanFree)
- & (gChan3^.theChan.userInfo = kChanFree)
- & (gChan4^.theChan.userInfo = kChanFree) THEN BEGIN
- gCalledBack:= FALSE;
- gChanOpen:= FALSE; {no longer making noises}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE FreeAllChans;
-
- {This is the routine that will force all channels to be released. It also
- resets the gCalledBack flag. This is used by all routines just before
- opening a new channel to force all channels to be disposed.}
-
- BEGIN
- FreeChan(gChan1);
- FreeChan(gChan2);
- FreeChan(gChan3);
- FreeChan(gChan4);
- gCalledBack:= FALSE;
- gChanOpen:= FALSE; {no longer making noises}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- PROCEDURE FreeSoundUnit;
-
- {This is the final routine to be called by the application when it is has
- finished using this unit. This will dispose of all the channels and
- memory used by this unit.}
-
- BEGIN
- FreeAllChans;
- IF gChan1 <> NIL THEN
- DisposPtr(Ptr(gChan1));
- IF gChan2 <> NIL THEN
- DisposPtr(Ptr(gChan2));
- IF gChan3 <> NIL THEN
- DisposPtr(Ptr(gChan3));
- IF gChan4 <> NIL THEN
- DisposPtr(Ptr(gChan4));
- gCalledBack:= FALSE;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoCallBack(chan: SndChannelPtr; theCmd: SndCommand);
-
- {This will be called at interrupt time by the Sound Manager when it
- receives a callBackCmd. I use the second parameter of the command to hold
- my application’s A5 reference. I first set up A5 so that I can access my
- globals. I mark the given channel as being complete and set gCalledBack
- to TRUE. This lets the application know that the callBackCmd has been
- processed. The callBackCmd can be used for other purposes, and the first
- parameter of the command could be a flag to a more extensive routine.
- Synchronizing the application with the channel is possible with this
- method.
-
- WARNING: This routine MUST be resident in memory and cannot make a call
- to a non-resident segment. I put this into the Main segment because of
- this.
-
- BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
- 'snd '. A bogus callBackCmd is placed into the queue immediately after
- the bufferCmd used to play the sound. This bogus callBackCmd will cause
- my callBackProc to be called when I wasn’t expecting it. I have been
- using the command’s second parameter to contain my A5 address. If I’m
- given a bogus callBackCmd, it would be really bad to set A5 address to
- this bogus parameter in the command. I found that the bogus callBackCmd
- contains the handle to the 'snd ' passed in to _SndPlay. I also found
- that param1 contains the handle’s state bits (results of HGetState). To
- work with this bug I set my real callBackCmd’s param1 to a specific value
- when I installed it into the queue. See the SoundComplete routine. Then
- I test the callBackCmd to make sure I’m dealing with the real one.}
-
- VAR
- theA5: LONGINT;
-
- BEGIN
- IF thecmd.param1 = kSoundComplete THEN BEGIN {if it’s my callBackCmd}
- theA5:= SetA5(theCmd.param2); {refer to tech note 208}
- chan^.userInfo:= kChanComplete; {this channel is done}
- gCalledBack:= TRUE;
- theA5:= SetA5(theA5); {restore original A5}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SoundComplete(chan: SndChannelPtr): OSErr;
-
- {I use this to install the callBackCmd into the given channel. I need to
- pass in our A5 along with the command so that the callBack routine can
- access my globals. I also wait until the channel is ready for another
- command in the case of the channel being full. Once the Sound Manager
- calls my call back procedure I will dispose of the channel. So, this is
- the last sound command to be sent to a channel. I pass to the call back
- A5 in the second parameter of the callBackCmd. Refer to Tech Note #208.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= callBackCmd;
- param1:= kSoundComplete;
- param2:= SetCurrentA5;
- END;
- SoundComplete:= SndDoCommand(chan, theCmd, kWait);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION ChanAvailable(theChan: SndChannelPtr): OSErr;
-
- {This routine will test the given channel to see if it will really produce
- sound. The Sound Manager in System 6.0x will return noErr even if the
- channel isn’t going to work. In the future the Sound Manager will return
- the proper error. Until then, I use this routine to determine this for me.
- There can only be a single channel at any time, unless I have the wave
- table synthesizer open. This will allow four channels. Channels have
- a pointer to the next channel, and if this is not NIL I suspect the
- given channel will not work. I test the given channel for being a
- wave type, and if so I need to see if the other channels I’ve got are
- also wave type. If it doesn’t look like the channels is available, I
- return badChannel. It is important to set the userInfo field of a channel
- before calling this routine!
-
- BUG NOTE: If an application is not using the Sound Manager and instead
- uses the older Sound Driver, any given channel will fail. Or if the other
- application does not release is channels, then my channels will not work.
- The most noticeable offender of this is HyperCard. Friendly applications
- will dispose of their channels at suspend/resume times or ASAP.}
-
- FUNCTION IsMyChan(chan: SndChannelPtr): BOOLEAN;
- BEGIN
- IsMyChan:= ((chan = SndChannelPtr(gChan1)) {is it one of ours?}
- | (chan = SndChannelPtr(gChan2))
- | (chan = SndChannelPtr(gChan3))
- | (chan = SndChannelPtr(gChan4)));
- END;
-
- FUNCTION CompatibleChan(waveChan: SndChannelPtr): BOOLEAN;
- BEGIN
- CompatibleChan:= (waveChan^.userInfo = waveTableSynth) {wave or..}
- | (waveChan^.userInfo = kChanFree); {free chan}
- END;
-
- BEGIN
- ChanAvailable:= noErr;
- IF theChan^.nextChan <> NIL THEN BEGIN {looks bad}
- ChanAvailable:= badChannel; {prepare to fail}
- IF theChan^.userInfo = waveTableSynth THEN BEGIN {last attempt}
- IF IsMyChan(theChan^.nextChan)
- & CompatibleChan(SndChannelPtr(gChan1))
- & CompatibleChan(SndChannelPtr(gChan2))
- & CompatibleChan(SndChannelPtr(gChan3))
- & CompatibleChan(SndChannelPtr(gChan4))
- THEN
- ChanAvailable:= noErr; {got lucky}
- END;
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SndDataAvailable(sndHandle: Handle): OSErr;
-
- {Given a resource handle, this will attempt to load it into memory. If the
- data is not available, then return an error.}
-
- BEGIN
- SndDataAvailable:= noErr;
- IF sndHandle <> NIL THEN BEGIN
- LoadResource(sndHandle);
- IF sndHandle^ = NIL THEN
- SndDataAvailable:= nilHandleErr; {master pointer is NIL}
- END ELSE
- SndDataAvailable:= nilHandleErr; {user passed a NIL handle}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION HoldSnd(sndHandle: Handle): OSErr;
-
- {This is used to put the given sound resource into memory and hold it there.
- I also use a MoveHHi to keep the heap from being fragmented. If this
- fails, then I return an error. I dereference the handle and check if the
- master pointer is NIL. This would mean the data could not be loaded.}
-
- BEGIN
- IF sndHandle <> NIL THEN BEGIN
- LoadResource(sndHandle);
- IF sndHandle^ = NIL THEN
- HoldSnd:= nilHandleErr {master pointer is NIL}
- ELSE BEGIN
- HoldSnd:= noErr;
- MoveHHi(sndHandle);
- HLock(sndHandle);
- END;
- END ELSE
- HoldSnd:= nilHandleErr; {user passed a NIL handle}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSynthInfo(sndHandle: Handle): SynthInfo;
-
- {This routine will return the 'snth' resource ID specified by the sound.
- I use this to determine if the given sound will work with _SndPlay.
- This routine does not require the data to be in memory when called. It
- also doesn’t lock it down while looking for the information. I will
- mark the resource as being purgeable in the case the resource attributes
- has it’s non-purgeable bit set.}
-
- VAR
- soundPtr: Ptr;
- theErr: OSErr;
-
- BEGIN
- GetSynthInfo.synthID:= kNoSynth;
- GetSynthInfo.initOption:= 0;
- theErr:= SndDataAvailable(sndHandle);
- IF theErr = noErr THEN BEGIN
- soundPtr:= sndHandle^;
- IF Snd1HdrPtr(soundPtr)^.format = firstSoundFormat THEN BEGIN
- IF Snd1HdrPtr(soundPtr)^.numSynths <> kNoSynth THEN BEGIN
- soundPtr:= Ptr(ORD4(soundPtr) + SizeOf(Snd1Header));
- GetSynthInfo.synthID:= SynthInfoPtr(soundPtr)^.synthID;
- GetSynthInfo.initOption:= SynthInfoPtr(soundPtr)^.initOption;
- END;
- END ELSE BEGIN {snd is a format 2 for HyperCard}
- GetSynthInfo.synthID:= sampledSynth;
- GetSynthInfo.initOption:= 0; {no options currently supported}
- END;
- HPurge(sndHandle);
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSndDataOffset(sndHandle: Handle;
- VAR dataType, waveLength: INTEGER): LONGINT;
-
- {This routine will cruise through the given snd resource. It will locate
- the sound data, if any, and return its type and offset into the resource.
- I prefer to return an offset instead of a pointer because I don’t want
- to have the data locked in memory. If I return an offset, the caller
- can decide when and if it wants the resource locked down to access the
- sound data. The first step in finding this data is to determine if I’m
- looking at a format 1 or 2 type snd. A type 2 is easy, but a type 1 will
- require me to find the number of snths specified and then to skip over
- each one including the init option. Once this is done, I have a pointer
- to the number of commands in the snd. When I’ve found the first one, I
- examine it to find out if it is a sound data command. Being it’s a sound
- resource, the command will also have its dataPointerFlag set. Once I’ve
- found a command I’m looking for I return its type and offset, then get out
- of the REPEAT block. OTHERWISE I go on to the next command. All of this
- makes it possible to get the sound data for use as an instrument sound.
- Typically this will be a sampled sound.
-
- WARNING: Do not send this routine a NIL handle.}
-
- VAR
- synths,
- howManyCmds: INTEGER;
- cruisePtr: Ptr;
-
- BEGIN
- GetSndDataOffset:= 0;
- dataType:= kNoSynth;
- waveLength:= 0;
- cruisePtr:= sndHandle^;
- IF cruisePtr <> NIL THEN BEGIN
- IF Snd1HdrPtr(cruisePtr)^.format = firstSoundFormat THEN BEGIN
- synths:= Snd1HdrPtr(cruisePtr)^.numSynths;
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd1Header));
- cruisePtr:= Ptr(ORD4(cruisePtr) + (SizeOf(SynthInfo) * synths));
- END ELSE
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(Snd2Header));
- howManyCmds:= IntPtr(cruisePtr)^; {pointing at number of cmds}
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(howManyCmds));
-
- {cruisePtr is now at the first sound command}
- REPEAT {cruise all commands and find a soundCmd or bufferCmd}
- CASE SndCmdPtr(cruisePtr)^.cmd OF
-
- (soundCmd + dataPointerFlag), (bufferCmd + dataPointerFlag):
- BEGIN
- dataType:= sampledSynth;
- GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
- howManyCmds:= 0; {done, get out of loop}
- END;
-
- (waveTableCmd + dataPointerFlag):
- BEGIN
- dataType:= waveTableSynth;
- waveLength:= SndCmdPtr(cruisePtr)^.param1;
- GetSndDataOffset:= SndCmdPtr(cruisePtr)^.param2;
- howManyCmds:= 0; {done, get out of loop}
- END;
-
- OTHERWISE {catch any other type of cmd}
- BEGIN
- cruisePtr:= Ptr(ORD4(cruisePtr) + SizeOf(sndCommand));
- howManyCmds:= howManyCmds - 1;
- END;
-
- END;
- UNTIL howManyCmds < 1; {done with all the commands}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION InstallSampleSnd(myChan: MyChanPtr; sndHandle: Handle): OSErr;
-
- {Given a channel and sampled sound resource, this routine will install the
- sound into the channel for use as an instrument. This allows an
- application to send noteCmds to the channel and play a melody. If I sent
- a bufferCmd instead of the soundCmd, the Sound Manager would play the
- sampled sound. This is basically what _SndPlay would do with a format 2
- snd. I insure that I am using only the proper buffer format having the
- standard encode option. If I were to support compressed sounds, I would
- have to call the MACE synthesizers to expand the buffer before I can use
- it as an instrument. If I don’t get a sampled sound of standard encoding
- I’ll return a bad format error. I use _SndDoImmediate to get the sound
- installed because I don’t want this command to be queued.}
-
- VAR
- theCmd: sndCommand;
- dataPtr: MySndHeaderPtr;
- dataOffset: LONGINT;
- sndDataType,
- ignore: INTEGER;
- theErr: OSErr;
-
- BEGIN
- theErr:= HoldSnd(sndHandle);
- IF theErr = noErr THEN BEGIN
- dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
- dataPtr:= MySndHeaderPtr(ORD4(sndHandle^) + dataOffset);
- IF (sndDataType = sampledSynth) & (stdSH = dataPtr^.encode) THEN BEGIN
- WITH theCmd DO BEGIN
- cmd:= soundCmd;
- param1:= 0;
- param2:= ORD4(dataPtr);
- END;
- myChan^.dataHandle:= sndHandle;
- theErr:= SndDoImmediate(SndChannelPtr(myChan), theCmd);
- END ELSE BEGIN
- theErr:= badFormat; {return a bad format error}
- HUnlock(sndHandle); {and free up the resource}
- HPurge(sndHandle);
- END;
- END;
- InstallSampleSnd:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetSampleChan(VAR sampleChan: SndChannelPtr; init: LONGINT;
- sndInstrument: Handle): OSErr;
-
- {This routine will create a sampled sound channel using the INIT option
- given. Typically this will be 0. In any case with System 6.0x this
- option is ignored by the sampled sound synthesizer. The given sound
- resource will be installed into the channel for use as an instrument.
-
- WARNING: If the application does not want an instrument sound, then the
- sndInstrument handle MUST be passed in as NIL.
-
- BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
- a Memory Manager error when allocating its internal buffer. There is a
- call to NewPtr(1316) and if a NIL is returned, the Sound Manager will
- write randomly to low memory. This can occur when calling _SysBeep under
- low memory conditions. Also, this pointer is allocated into the
- application’s heap instead of the system’s.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- FreeAllChans;
- theErr:= SndNewChannel(SndChannelPtr(gChan1), sampledSynth,
- init, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= sampledSynth;
- theErr:= ChanAvailable(SndChannelPtr(gChan1));
- IF (theErr = noErr) & (sndInstrument <> NIL) THEN
- theErr:= InstallSampleSnd(gChan1, sndInstrument);
- END;
- IF theErr <> noErr THEN
- FreeAllChans
- ELSE
- gChanOpen:= TRUE; {got an open channel}
- sampleChan:= SndChannelPtr(gChan1);
- GetSampleChan:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION InstallWave(waveChan: SndChannelPtr; aWavePtr: Ptr;
- waveLength: INTEGER): OSErr;
-
- {Given a channel and pointer to a wave table, this will install the wave
- for use as an instrument into the channel. If I find the application
- giving me a NIL pointer, I’ll return an error. I use _SndDoImmediate
- to get the sound installed because I don’t want this to be queued.}
-
- VAR
- theCmd: SndCommand;
-
- BEGIN
- IF aWavePtr <> NIL THEN BEGIN
- WITH theCmd DO BEGIN
- cmd:= waveTableCmd;
- param1:= waveLength;
- param2:= ORD4(aWavePtr);
- END;
- InstallWave:= SndDoImmediate(waveChan, theCmd);
- END ELSE
- InstallWave:= memPCErr; {Pointer Check failed}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetWaveChans(VAR waveChan1, waveChan2,
- waveChan3, waveChan4: SndChannelPtr): OSErr;
-
- {This will return four wave table channels with their waves installed.
- When I create a channel I will set the userInfo field marking it with the
- 'snth' associated to it. This must be done before calling ChanAvailable.
- Otherwise that test will fail. If I cannot obtain all four wave channels
- I will dispose of the ones I did get before returning the error. This
- routine expects to find four wave table pointers, or it will fail.}
-
- VAR
- theErr: OSErr;
-
- PROCEDURE NewWaveChan(VAR myChan: MyChanPtr; init: INTEGER);
- BEGIN
- theErr:= SndNewChannel(SndChannelPtr(myChan), waveTableSynth,
- init, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- myChan^.theChan.userInfo:= waveTableSynth;
- theErr:= ChanAvailable(SndChannelPtr(myChan));
- END;
- END;
-
- BEGIN
- FreeAllChans;
- NewWaveChan(gChan1, initChan0);
- IF theErr = noErr THEN BEGIN
- NewWaveChan(gChan2, initChan1);
- IF theErr = noErr THEN BEGIN
- NewWaveChan(gChan3, initChan2);
- IF theErr = noErr THEN BEGIN
- NewWaveChan(gChan4, initChan3);
- END;
- END;
- END;
- IF theErr <> noErr THEN
- FreeAllChans {we didn’t make it}
- ELSE BEGIN
- waveChan1:= SndChannelPtr(gChan1);
- waveChan2:= SndChannelPtr(gChan2);
- waveChan3:= SndChannelPtr(gChan3);
- waveChan4:= SndChannelPtr(gChan4);
- gChanOpen:= TRUE; {now we’re making noise}
- END;
- GetWaveChans:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetNoteChan(VAR noteChan: SndChannelPtr; timbre: INTEGER): OSErr;
-
- {This will create a channel for the note synthesizer. When I create a
- channel I will set the userInfo field marking it with the 'snth'
- associated to it. This must be done before calling ChanAvailable.
- Otherwise that test will fail. There are no INIT options used by this
- synthesizer, but I will set the timbre to adjust the tone quality.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- FreeAllChans;
- theErr:= SndNewChannel(SndChannelPtr(gChan1), noteSynth,
- kInitNone, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= noteSynth;
- theErr:= ChanAvailable(SndChannelPtr(gChan1));
- IF theErr = noErr THEN
- theErr:= SetNoteTimbre(SndChannelPtr(gChan1), timbre, NOT kWait);
- END;
- IF theErr <> noErr THEN
- FreeAllChans
- ELSE
- gChanOpen:= TRUE; {now we’re making noise}
- noteChan:= SndChannelPtr(gChan1);
- GetNoteChan:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION GetNoSynthChan(VAR chan: SndChannelPtr): OSErr;
-
- {This is the routine to create a channel that isn’t associated with any
- synthesizer. Why? Because if you wanted to use _SndPlay asynchronously
- you need to get such a channel. When I create a channel I will set the
- userInfo field marking it with the 'snth' associated to it. This must be
- done before calling ChanAvailable.
-
- BUG NOTE: Do not use a channel already associated to a snth with
- _SndPlay. This causes the Sound Manager to install a second copy of the
- same snth.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- FreeAllChans;
- theErr:= SndNewChannel(SndChannelPtr(gChan1), kNoSynth,
- kInitNone, @DoCallBack);
- IF theErr = noErr THEN BEGIN
- gChan1^.theChan.userInfo:= kNoSynth;
- theErr:= ChanAvailable(SndChannelPtr(gChan1));
- END;
- IF theErr <> noErr THEN
- FreeAllChans
- ELSE
- gChanOpen:= TRUE; {now we’re making noise}
- chan:= SndChannelPtr(gChan1);
- GetNoSynthChan:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION PlaySong(chan: SndChannelPtr; sndSong: Handle): OSErr;
-
- {This routine will use the given channel and snd resource with _SndPlay.
- This is used to play a sound, which is a series of sound commands commonly
- referred to as a sequence. First thing I do is make sure the song fits in
- memory. _SndPlay will lock this resource in memory and then pump the snd
- for all of its worth. I am calling it asynchronously, and if I was using
- a snd that contained sound data I wouldn’t mark the snd as being
- purgeable. But in this case, _SndPlay will be done with the snd as soon
- as it returns because it copied all of the commands into the channel.
- (There’s no data associated with a sequence, just commands.) _SndPlay
- will not return until it has done so. After _SndPlay I need to work
- around a bug in the noteCmd. The last thing to do is to send a
- callBackCmd to signal me that the channel has completed. If any Sound
- Manager errors are encountered, I return them to the application. If the
- application passed me a NIL snd handle, I’ll return an error.
-
- WARNING: Make sure you are using a snd that only has note type commands
- in it and not something such as a bufferCmd.
-
- BUG NOTE: There is problem when the final sound command is a noteCmd.
- The note will continue to sound, looping forever, until a quietCmd is sent
- or the channel is disposed of. To prevent unwanted looping, I send a
- quietCmd after all notes. Also read a related bug note when disposing of
- channels in the routine FreeChan.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- theErr:= SndDataAvailable(sndSong); {get the data loaded}
- IF theErr = noErr THEN BEGIN
- theErr:= SndPlay(chan, sndSong, kSMAsynch); {pump the sound}
- HUnlock(sndSong);
- HPurge(sndSong);
- IF theErr = noErr THEN BEGIN
- theErr:= SendQuiet(chan, kWait); {work around bug}
- IF theErr = noErr THEN
- theErr:= SoundComplete(chan);
- END;
- END ELSE
- PlaySong:= nilHandleErr; {snd data was not available}
- IF theErr <> noErr THEN
- FreeAllChans;
- PlaySong:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION ReleaseSynch(chan: SndChannelPtr): OSErr;
-
- {This is used to send a syncCmd to a channel and causes the other channels
- that are being held by a synchCmd to be released. Of course, this assumes
- the application has already called SynchChans. _SndDoImmediate is used
- to get the command directly to the synthesizer bypassing the queue.
-
- BUG NOTE: I’ve found that immediately clearing the channels and starting
- new ones may cause the channels to startup playing out of synch? This
- happens while disposing the wave channels and starting them immediately.}
-
- VAR
- theCmd: sndCommand;
-
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= syncCmd;
- param1:= 1;
- param2:= kSyncID;
- END;
- ReleaseSynch:= SndDoImmediate(chan, theCmd);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION SynchChans(chan1, chan2, chan3, chan4: SndChannelPtr): OSErr;
-
- {This is used to synchronize four wave table channels. By first sending
- the synchCmd, I can send a sequence of other commands to the channel and
- not have the channel attempt to start processing any of them. That is until
- another synchCmd is sent causing all of the previous synchCmd’s counter
- to be decremented. After getting all the channels in synch and sending
- the sequence of further commands, then use the ReleaseSynch routine to
- start all of the channels processing their respective queues.
- _SndDoImmediate is used to get the command directly to the synthesizer
- bypassing the queue.}
-
- VAR
- theCmd: SndCommand;
- theErr: OSErr;
-
- PROCEDURE Synch1Chan(chan: SndChannelPtr; count: INTEGER);
- BEGIN
- WITH theCmd DO BEGIN
- cmd:= syncCmd;
- param1:= count;
- param2:= kSyncID;
- END;
- theErr:= SndDoImmediate(chan, theCmd);
- END;
-
- BEGIN
- Synch1Chan(chan4, 5);
- IF theErr = noErr THEN BEGIN
- Synch1Chan(chan3, 4);
- IF theErr = noErr THEN BEGIN
- Synch1Chan(chan2, 3);
- IF theErr = noErr THEN BEGIN
- Synch1Chan(chan1, 2);
- END;
- END;
- END;
- SynchChans:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION Play4Waves(waveChan1, waveChan2, waveChan3, waveChan4: SndChannelPtr;
- song1, song2, song3, song4: Handle): OSErr;
-
- {In order to synchronize channels, the synchCmd is needed. Once all of the
- song has been sent into each channel, a final synchCmd is issued to
- release them. Don’t send more commands into a channel that it can hold at
- one time while the channel is in synch mode.}
-
- VAR
- theErr: OSErr;
-
- BEGIN
- theErr:= SynchChans(waveChan1, waveChan2, waveChan3, waveChan4);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan1, song1);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan2, song2);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan3, song3);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(waveChan4, song4);
- IF theErr = noErr THEN
- theErr:= ReleaseSynch(waveChan1);
- END;
- END;
- END;
- END;
- Play4Waves:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION HyperSndPlay(sndHandle: Handle): OSErr;
-
- {WARNING: IT IS RECOMMENDED THAT YOU DO NOT USE THIS CODE. I’ve provided
- this routine because people have asked me how HyperCard performs its PLAY
- command and why their HyperCard sounds do not sound right using the
- _SndPlay routine. The correct answer is that _SndPlay plays the sound
- correctly. HyperCard is attempting to change the frequency by adjusting
- the sample rate. This is NOT the correct approach. Define the sound
- buffer as it should be played. _SndPlay plays the sound as it is defined.
- If the result from _SndPlay is not what you want, then it is the sample
- that is incorrect and should be edited. The sample rate is the rate at
- which the sound was recorded. If you didn’t record it, then how do you
- know what’s the correct rate? Set the baseNote to the note that was
- recorded. If you recorded middle C at 22k, then the rate is 22k and the
- baseNote is middle C. HyperCard is incorrect in using the sample rate as
- the frequency of the sound. Furthermore, using this technique of
- calculating a new sample rate can introduce errors. The resulting sample
- rate will not be the proper pitch. Also, the sample rate for high pitches
- will be very inaccurate and impossible for the Mac to reproduce. Such a
- problem can happen if the given sample rate was 22k and is to be played
- back at three octaves higher. Even 44k samples transposed up a half
- octave will fail. Using the soundCmd and noteCmd will not have this
- problem.
-
- Given a sound resource, this routine will play it in the manner that
- HyperCard does. HyperCard assumes that a sound is to be played at middle
- C when the user does not specify a note value in the PLAY command. I
- don’t know why. (What’s middle C when I want to hear speech or the sound
- of crickets?) At any rate (pun intended), I get a sampled sound channel.
- I find the sound data offset in the resource, which has to be locked down
- at this time. Once I have the sound data, I get its original sample rate.
- I have to calculate what a new sample rate would be based on its baseNote.
- The baseNote is the note at which the sound was recorded. I’m not sure
- what this means to crickets, but if this is set to middle C then HyperCard
- doesn’t attempt to modify the sample rate. (If you’re wondering how the
- math works in this routine, buy a book on music theory. I’m here to
- provide Mac support.) Once I’ve adjusted the sample rate, I use the
- bufferCmd to play it. Then I restore the sound resource to its original
- state. If I didn’t do this it would be possible that the resource was
- still in memory the next time I use it having the adjusted sample rate.
- This would cause me to incorrectly adjust it again. Unlike HyperCard, I
- can do this for both a format 1 and 2.
-
- BUG NOTE: Do not call SANE while the Sound Manager is running. Refer to
- Tech Note #235.}
-
- VAR
- theCmd: SndCommand;
- theErr: OSErr;
- dataOffset: LONGINT;
- sndDataType,
- power, ignore: INTEGER;
- dataPtr: MySndHeaderPtr;
- newRate: Extended;
- oldRate: Fixed;
-
- BEGIN
- theErr:= HoldSnd(sndHandle);
- IF theErr = noErr THEN BEGIN
- theErr:= GetSampleChan(SndChannelPtr(gChan1), kInitNone, NIL);
- gChan1^.dataHandle:= sndHandle; {so FreeAllChans can dispose of data}
- IF theErr = noErr THEN BEGIN
-
- dataOffset:= GetSndDataOffset(sndHandle, sndDataType, ignore);
- dataPtr:= MySndHeaderPtr(ORD4(sndHandle^) + dataOffset);
- IF (sndDataType = sampledSynth) & (stdSH = dataPtr^.encode) THEN BEGIN
-
- oldRate:= dataPtr^.sampleRate; {save original sample rate}
- IF NOT (dataPtr^.baseNote = kMiddleC) THEN BEGIN
- IF dataPtr^.sampleRate < 0 THEN {large positive number}
- newRate:= (Fix2X(BAnd(dataPtr^.sampleRate, maxLongInt))
- + maxLongInt + 1)
- ELSE
- newRate:= Fix2X(dataPtr^.sampleRate);
- newRate:= Fix2X(dataPtr^.sampleRate);
- power:= kMiddleC - dataPtr^.baseNote;
- dataPtr^.sampleRate:= X2Fix((XpwrI(twelthRootTwo, power)) * newRate);
- END;
- WITH theCmd DO BEGIN
- cmd:= bufferCmd;
- param1:= 0;
- param2:= ORD4(dataPtr);
- END;
- theErr:= SndDoImmediate(SndChannelPtr(gChan1), theCmd);
- IF theErr = noErr THEN
- theErr:= SoundComplete(SndChannelPtr(gChan1));
- dataPtr^.sampleRate:= oldRate; {restore original sample rate}
- END ELSE BEGIN
- theErr:= badFormat;
- HUnlock(sndHandle);
- HPurge(sndHandle);
- END;
- END;
- END;
- IF theErr <> noErr THEN
- FreeAllChans;
- HyperSndPlay:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S SoundUnit}
- FUNCTION AsynchSndPlay(sndHandle: Handle): OSErr;
-
- {Given a sound resource, this routine will call _SndPlay. The snd must
- be either a format 2 or format 1 that contains snth information.
- Using _SndPlay asynchronously requires us to lock the snd prior to
- calling the trap. The reason being is _SndPlay remembers the state of the
- lock bit using _HGetState and _HSetState. If the snd is unlocked when
- it’s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
- This would be bad when using the sound asynchronously. If the sound being
- passed in happens to be a compressed sound created with MACE, it will “do
- the right thing.” If MACE isn’t around the Sound Manager will pretend to
- play a sound but nothing will be heard.
-
- BUG NOTE: The sampled sound synthesizer in System 6.0x does not check for
- a Memory Manager error when allocating its internal buffer. There is a
- call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
- randomly to memory. Also, the pointer is allocated into the application’s
- heap instead of the system’s.
-
- BUG NOTE: _SndPlay when using System 6.0.4 and a sampled sound will send
- a bogus callBackCmd into the channel. This will cause the user’s call
- back procedure to be called as soon as the sound has completed. Refer
- to the DoCallBack routine for details.}
-
- VAR
- theErr: OSErr;
- synthID: INTEGER;
-
- BEGIN
- theErr:= HoldSnd(sndHandle); {hold on to the sound}
- IF theErr = noErr THEN BEGIN
- theErr:= GetNoSynthChan(SndChannelPtr(gChan1));
- gChan1^.dataHandle:= sndHandle; {so FreeAllChans can dispose of data}
- IF theErr = noErr THEN BEGIN
- synthID:= GetSynthInfo(sndHandle).synthID;
- IF synthID <> kNoSynth THEN BEGIN
- gChan1^.theChan.userInfo:= synthID;
- theErr:= SndPlay(SndChannelPtr(gChan1), sndHandle, kSMAsynch);
- IF theErr = noErr THEN
- theErr:= SoundComplete(SndChannelPtr(gChan1));
- END ELSE
- theErr:= badFormat;
- END;
- END;
- IF theErr <> noErr THEN
- FreeAllChans;
- AsynchSndPlay:= theErr;
- END;
-
-
- END. {UNIT}